package save;

import com.github.pagehelper.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ScrolledPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

/**
 * @author bushuai
 * @description es操作基础类
 * @create 2019-03-21 15:47
 **/
public abstract class BaseEsEnhanceService<T, Q> {

    protected Logger logger;

    private Class<T> clazz;

    // 导出最大2w
    protected int maxTotal = 20000;

    // 最大查询条数阈值，如果超过这个则用scroll游标查询，提高效率
    protected int esScrollLimitValue = 5000;

    // scroll游标查询的过期时间
    protected int esScrollTimeValue = 1000;

    @Autowired
    protected ElasticsearchTemplate elasticsearchTemplate;

    private final static FastDateFormat DATE_FORMAT =
            FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("GMT+00:00"));

    public BaseEsEnhanceService() {
        ParameterizedType genericSuperclass = (ParameterizedType) this.getClass().getGenericSuperclass();
        Type[] type = genericSuperclass.getActualTypeArguments();
        this.clazz = (Class<T>) type[0];
        this.logger = LoggerFactory.getLogger(this.getClass());
        this.init();
    }

    /** 构建查询条件
     * @param q
     * @return
     */
    public abstract QueryBuilder builderQuery(Q q);

    /**
     * 索引名
     *
     * @return
     */
    public abstract String getEsIndexName();

    /**
     * 类型名
     *
     * @return
     */
    public abstract String getEsTypeName();

    /**
     * 需要排序的覆盖此方法
     *
     * @return
     */
    protected SortBuilder getSortBuilder() {
        //设置排序
        return null;
    }

    /**
     * 用来初始化参数 否则使用默认参数
     */
    protected void init() {
    }

    /**
     * 分页查询
     * @param q 条件
     * @param pageInfo
     * @return
     */
    public Page<T> queryForPage(Q q, PageInfo pageInfo) {
        long pageNum = pageInfo.getPage();
        SearchQuery searchQuery = getSearchQuery(q, pageInfo);
        logger.info("\n{}:根据条件分页查询: \n 查询条件 = {} \n DSL  = \n {}", this.getClass().getSimpleName(), JsonUtil.toJsonString(q), searchQuery.getQuery().toString());

        List<T> pageContent = new ArrayList<>();
        Long totalElements = 0L;

        if (pageNum * pageInfo.getRows()  > esScrollLimitValue) {

            if (pageNum * pageInfo.getRows() > maxTotal) {
                return new PageData<>(pageContent, totalElements);
            }

            ScrolledPage<T> scroll = (ScrolledPage<T>) elasticsearchTemplate.startScroll(esScrollLimitValue, searchQuery, clazz);
            totalElements = scroll.getTotalElements();
            long scrollPage = 1;
            while (scroll.hasContent()) {
                if (scrollPage == pageNum) {
                    pageContent.addAll(scroll.getContent());
                    break;
                }
                scroll = (ScrolledPage<T>) elasticsearchTemplate.continueScroll(scroll.getScrollId(), esScrollLimitValue, clazz);
                scrollPage++;
            }
            elasticsearchTemplate.clearScroll(scroll.getScrollId());
        } else {
            Page<T> page = elasticsearchTemplate.queryForPage(searchQuery, clazz);
            pageContent = page.getContent();
            totalElements = page.getTotalElements();
        }

        return new PageData<>(pageContent, totalElements);
    }

    /**
     * @param q 查询条件
     * @return T
     */
    public List<T> queryForList(Q q) {
        SearchQuery searchQuery = getSearchQuery(q, null);

        logger.info("\n{}:ES根据条件查询所有: \n 查询条件 = {} \n DSL  = \n {}", this.getClass().getSimpleName(), JsonUtil.toJsonString(q), searchQuery.getQuery().toString());
        long count = elasticsearchTemplate.count(searchQuery);
        List<T> list = new ArrayList<>();
        if (count == 0) {
            return list;
        }
        if (count <= esScrollLimitValue) {

            list = elasticsearchTemplate.queryForList(searchQuery, clazz);
        } else { //大于ES_SCROLL_LIMIT_VALUE，则用滚动查询
            ScrolledPage<T> scroll = (ScrolledPage<T>) elasticsearchTemplate.startScroll(esScrollLimitValue, searchQuery, clazz);
            while (scroll.hasContent()) {
                if (list.size() >= maxTotal) {
                    break;
                }
                list.addAll(scroll.getContent());
                scroll = (ScrolledPage<T>) elasticsearchTemplate.continueScroll(scroll.getScrollId(), esScrollLimitValue, clazz);
            }
            elasticsearchTemplate.clearScroll(scroll.getScrollId());
        }
        return list;

    }

    /**
    * @Description: 不翻页
    * @Author: bushuai
    * @Date: 2019/4/3 16:48
    */
    public List<T> queryForListNotPage(Q q){
        SearchQuery searchQuery = getSearchQuery(q, null);
        return elasticsearchTemplate.queryForList(searchQuery, clazz);
    }


    /**
     * 查询条件
     *
     * @param q
     * @return
     */
    public long findTotalCount(Q q) {
        SearchQuery searchQuery = getSearchQuery(q, null);
        logger.info("\n{}：ES根据条件查询总数量: \n 查询条件 = {} \n DSL  = \n {}", this.getClass().getSimpleName(), JsonUtil.toJsonString(q),searchQuery.getQuery().toString());
        return elasticsearchTemplate.count(searchQuery);
    }





    /**
     * 构建查询条件
     * @param q
     * @param pageInfo
     * @return
     */
    private SearchQuery getSearchQuery(Q q, PageInfo pageInfo) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        QueryBuilder query = builderQuery(q);
        SortBuilder sort = getSortBuilder();
        if (sort != null) {
            //设置排序
            nativeSearchQueryBuilder.withSort(sort);
        }
        nativeSearchQueryBuilder.withIndices(this.getEsIndexName())
                .withTypes(this.getEsTypeName())
                .withQuery(query);

        //设置分页，如果不设置，ES默认只返回10条
        Pageable pageable;
        if (pageInfo != null) {
            pageable = PageRequest.of(pageInfo.getPage() - 1, pageInfo.getRows());
        } else {
            pageable = PageRequest.of(0, esScrollLimitValue);
        }
        nativeSearchQueryBuilder.withPageable(pageable);
        return nativeSearchQueryBuilder.build();
    }


    /**
     * 由于ES存储时是 GMT+00:00，及无时区信息，故在查询时需要转换成相同时区
     *
     * @author: lixiaoqiang
     * @date: 2018/11/29 17:41
     * @param: [dateStr]
     * @return: java.lang.String
     */
    public String convertDateTimeZone(String dateStr) {
        if (StringUtils.isBlank(dateStr)){
            return "";
        }
        return DATE_FORMAT.format(DateUtils.getDateByString(dateStr, "yyyy-MM-dd HH:mm:ss"));
    }
}






























